---
title: "7：プラグイン"
---

import PlusIcon from '@material-ui/icons/AddSharp'
import SvgPluginPipelines from './plugin-pipelines.svg';

これまでで、基本的なルーティングと404レスポンスを持ったプロキシに取り組んできました。しかし、ソースファイルにどんどんパイプラインを投げ込み、機能を追加していくうちに急速に肥大化してしまいました。そこで、次のプロキシ機能に移る前に、コード構造のリファクタリングを行った方がよさそうです。そうすれば、将来的に新しく追加される機能はすべて独立したファイルになり、オプションでシステムにフックすれば良いわけです。これが即ち、「_plugins_」です。

## ルータープラグイン

いま取り組んでいるプロキシの主な機能はルーティングです。ルーティングのコードは、`/proxy.js`の内容のほとんどを占めます。これをベースにして、最初のプラグイン「_router_」のコードを作成します。

1.  ボタン <PlusIcon/> をクリックして、新しいファイル名として`/plugins/router.js`を入力し、 **Create** をクリックします。

> 新しいファイルを追加する前に、フォルダを作成する必要はありません。フォルダ名を含めたフルパス名を入れるだけで、自動的にフォルダが作成されます。

2.	`/proxy.js`の全ての内容を`/plugins/router.js`にコピーします。

`/plugins/router.js`の内部では、ルーティングロジックは _demuxHTTP()_ の直後のパイプライン「_request_」でのみ作動します。そこで、 _demuxHTTP()_ とそのパイプライン全体をここから削除し、メインのスクリプトである`/proxy.js`に任せ、しばらくしてから「_request_」パイプラインにリンクするようにします。

``` js
- .listen(8000)
-   .demuxHTTP('request')

  .pipeline('request')
    .handleMessageStart(
      msg => (
        _target = _router.find(
          msg.head.headers.host,
          msg.head.path,
        )
      )
    )
    .link(
      'forward', () => Boolean(_target),
      '404'
    )
```

「_404_」のロジックは、スタンドアロンのプラグインにすることもでき、リクエストのハンドラーが他のどのプラグイン内にもない場合に「_default_」のハンドラーとして動作します。従って、今はこの「_404_」のパイプラインを削除して、後で別のプラグインでやり直します。

``` js
  .pipeline('connection')
    .connect(
      () => _target
    )

- .pipeline('404')
-   .replaceMessage(
-     new Message({ status: 404 }, 'No route')
-   )
```

ここにはもうパイプライン「_404_」がないので、それに応じて _link()_ でパイプラインへの参照を変更する必要があります。

``` js
    .link(
      'forward', () => Boolean(_target),
-     '404'
+      ''
    )
```

_link()_ 内のパイプライン名を空にすると、何もないところにリンクすることになります。この場合、 _link()_ を通るものはすべて何の変化もせず（`_target`がfalsyの場合のみ）、 _link()_ が存在しなかったのと同じになります。

## デフォルトプラグイン

では、上記で説明した「_default_」プラグインを作ってみましょう。

`/plugins/default.js`という名前のファイルを作って、以下のコードを書きます。

``` js
pipy()

.pipeline('request')
  .replaceMessage(
    new Message({ status: 404 }, 'No handler')
  )
```

ご覧の通り、これは簡単です。サブパイプラインは1つだけで、名前は同じく「_request_」です。これは、おなじみの「_404_」パイプラインと全く同じことをしますが、メッセージだけ「_No route_」から「_No handler_」に変更しています。

## プラグインのチェーン

では、メインのスクリプト`/proxy.js`に戻ります。殆どのコードをpluginに移したので、 _demuxHTTP()_ と空の「_request_」パイプラインだけが残っています。

``` js
pipy()

.listen(8000)
  .demuxHTTP('request')

.pipeline('request')
```

この空の「_request_」パイプラインでは、プラグインから他の2つの「_request_」パイプラインにチェーンを張っています。これを行うために、[_use()_](/reference/api/Configuration/use)を使ってパイプラインが存在するファイル名と、パイプラインがある場所の名前を与えます。

``` js
  .pipeline('request')
+   .use(
+     [
+       'plugins/router.js',
+       'plugins/default.js',
+     ],
+     'request',
+     'response',
+   )
```

「_request_」の他に、「_response_」という2つ目のパイプラインを与えたことが分かるでしょうか。これは、すべての「_request_」パイプラインの後に、ストリームを実行するパイプラインの名前ですが、プラグインリストでは逆の順番になっています。

_use()_ で作られた「_プラグインチェーン_」は次のようになります。

<div style="text-align: center">
  <SvgPluginPipelines/>
</div>

まだ、プラグインで「_response_」パイプラインを定義していませんが、問題ありません。プラグインにパイプライン名が見つからない場合は、チェーンは単純にスキップします。

## リクエストの下位移動

これで、プラグインにチェーンを張ることができました。しかし、まだ問題があります。入力されたリクエストはすべて、同じ`router.js`と`default.js`を通ります。たとえ`router.js`でアップストリームのサーバーにプロキシされ、レスポンスとして戻ってきたとしても、`default.js`の後、それらはすべて「_404 No handler_」に「_replaced_」されてしまいます。

この問題は、リクエストが処理されるかどうかを示すカスタムグローバル変数を使用して解決します。`router.js`でリクエストがプロキシされると、変数は`true`に設定され、 _use()_ にチェーンの上位への移動を停止し、チェーンの下位へ移動するように指示します。ここでは変数に`__turnDown`という名前を付けます。

最後の構成パラメーターで _use()_ に変数を与えます。これは動的な値であることを忘れないでください。関数でラップする必要があります。

``` js
  .pipeline('request')
    .use(
      [
        'plugins/router.js',
        'plugins/default.js',
      ],
      'request',
      'response',
+     () => __turnDown,
    )
```

### エクスポートとインポート

次に、グローバル変数を定義します。ただし、今までやってきたように定義することができません。それは、変数が`proxy.js`でのみ利用可能で、`router.js`にはその変数がないからです。そこで、他のファイルから「_import_」できるように、`proxy.js`の[_export()_](/reference/api/Configuration/export)で定義します。初期値は`false`です。

``` js
  pipy()

+ .export('proxy', {
+   __turnDown: false,
+ })
```

_export()_ の最初の構成パラメーターは、インポートしてくる他のファイルのネームスペースです。任意の名前をつけることができます。ここでは、意味合いから「_proxy_」が良いでしょう。

次に、この変数を`router.js`からインポートし、ルートが見つかったときに`true`に設定します。

``` js
+ .import({
+   __turnDown: 'proxy',
+ })

  .pipeline('request')
    .handleMessageStart(
      msg => (
        _target = _router.find(
          msg.head.headers.host,
          msg.head.path,
-       )
+       ),
+       _target && (__turnDown = true)
      )
    )
    .link(
      'forward', () => Boolean(_target),
      ''
    )
```

ネームスペースは、`__turnDown`がエクスポートされる`proxy.js`にあるもの、つまり「_proxy_」と一致する必要があることに注意してください。

## テストしてみる

今回は何ら新しい機能を追加していません。前回と同じように動作するはずです。

``` sh
$ curl localhost:8000/hi
Hi, there!
$ curl localhost:8000/ip
You are requesting /ip from ::ffff:127.0.0.1
$ curl localhost:8000/echo -d 'hello'
hello
$ curl localhost:8000/bad/path
No handler
```

## まとめ

このパートでは、それぞれの機能をそれぞれのプラグインに分離する方法を解説しました。これによって、今後プロキシの機能をさらに拡張するための強固な基盤ができました。

### 要点

1.	[_use()_](/reference/api/Configuration/use)を使用して、他のファイルで定義されているパイプラインにリンクし、プラグインチェーンを作ります。これは、拡張性のあるプラグインアーキテクチャの鍵になります。

2.	[_pipy()_](/reference/api/pipy)で定義されたグローバル変数は、その定義ファイル内でのみ利用することができます。異なるファイル間でグローバル変数を共有するには、[_export()_](/reference/api/Configuration/export)と[_import()_](/reference/api/Configuration/import)を使用します。

### 次のパートの内容

これで、美しいプラグインシステムを備えた、完全に機能するルーティングプロキシができましたが、まだ十分とは言えません。コード上には、リッスンするポートやルーティングテーブルなど、多くのパラメーターがあります。これらのパラメーターを個別の「_コンフィグレーション（設定）_」ファイルにまとめられれば、もっと適切に整理できるでしょう。次のパートでは、それについて解説します。

